// Copyright 2012. InMobi. All Rights reserved

package com.inmobi.sharedlock;

import java.util.concurrent.Semaphore;

/**
 * AsyncLocker can be used to lock objects in a different thread.
 *
 * Locks a given object for a given amount of time, once lock() is
 * called. lock() is returned once locking is setup. Call unlockNow()
 * to interrupt the original thread, and unlock the given object, if you
 * wish to stop locking before the time is over.
 */
public class AsyncLocker {

  private final Object sync;

  private final long timeout;

  private final Semaphore sequencer;

  private final Thread thread;

  /**
   * Locks the sync object, and sleeps for given time.
   */
  private class LockingRunnable implements Runnable {

    @Override
    public void run() {
      synchronized (sync) {
        sequencer.release();
        try {
          Thread.sleep(timeout);
        } catch (InterruptedException e) {
          // Stop right now!
          return;
        }
      }
    }
  }

  /**
   * Creates a new locker, but locking doesn't start until lock() is
   * called.
   * @param sync Object to synchronize on.
   * @param timeout amount of time to wait in milliseconds.
   */
  public AsyncLocker(Object sync, long timeout) {
    this.sync = sync;
    this.timeout = timeout;
    this.sequencer = new Semaphore(0);
    this.thread = new Thread(new LockingRunnable());
  }

  /**
   * Locks the object for given time.
   * @throws InterruptedException if the current thread is interrupt
   *   while waiting for locking setup to be done.
   */
  public void lock() throws InterruptedException {
    thread.start();
    sequencer.acquire();
  }

  /**
   * Unlocks the object immediately. The behaviour is undefined if you call this
   * before lock is invoked.
   */
  public void unlockNow() {
    thread.interrupt();
  }
}
